﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Reflection;
using System.ComponentModel;
using System.Collections;

namespace ProgNetComponents.Serialization
{
    /// <summary>
    /// ProgNetObjectSerializer
    /// </summary>
    public class ProgNetObjectSerializer
    {
        /// <summary>
        /// Gets or sets a value indicating whether [throw exceptions].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [throw exceptions]; otherwise, <c>false</c>.
        /// </value>
        public bool ThrowExceptions
        {
            get;
            set;
        }
        /// <summary>
        /// Serializes the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="throwExceptions">if set to <c>true</c> [throw exceptions].</param>
        /// <returns></returns>
        /// <exception cref="System.Exception">Error in Serialize Process</exception>
        public virtual string Serialize(object value, bool beautify = false)
        {
            XmlDocument doc = null;
            try
            {
                doc = new XmlDocument();
                var elThis = doc.AppendChildElement("this");
                elThis.AppendAttribute("Type", value.GetType().FullName);
                var properties = GetPropertiesToSerialize(value);
                foreach (PropertyInfo pi in properties)
                    AppendProperty(elThis, pi, value);
            }
            catch (Exception ex)
            {
                if (ThrowExceptions)
                    throw new Exception("Error in Serialize Process", ex);
            }
            if (beautify)
                return doc.ToFormatedString();
            return doc.InnerXml;
        }

        protected virtual void AppendProperty(XmlNode parent, PropertyInfo property, object propertyOwner)
        {
            object obj = property.GetValue(propertyOwner, null);
            AppendProperty(parent, property.Name, property.PropertyType, obj);
        }

        protected virtual XmlElement AppendProperty(XmlNode parent, string propertyName, Type propertyType, object propertyValue)
        {
            XmlElement el = parent.AppendChildElement(propertyName);
            if (propertyValue == null)
                el.AppendAttribute("IsNull", true);
            else
            {
                string strVal = string.Empty;
                 var mi = propertyType.GetMethod("Parse");
                 if (mi != null)
                 {
                     var converter = TypeDescriptor.GetConverter(propertyType);
                     if (converter != null && converter.CanConvertTo(typeof(string)))
                         strVal = (string)converter.ConvertTo(propertyValue, typeof(string));
                     else
                         strVal = propertyValue.AsString();
                 }
                 else if (propertyType.IsEnum)
                 {
                     strVal = propertyValue.AsString();
                 }
                 else
                 {
                     var serializer = SerializationTools.GetSerializer(propertyType);

                     if (serializer != null)
                     {
                         serializer.ThrowExceptions = ThrowExceptions;
                         strVal = serializer.Serialize(propertyValue);
                     }
                     else
                     {
                         var converter = TypeDescriptor.GetConverter(propertyType);
                         if (converter != null && converter.CanConvertTo(typeof(string)))
                             strVal = (string)converter.ConvertTo(propertyValue, typeof(string));
                         else
                             strVal = propertyValue.AsString();
                     }
                 }
                el.InnerXml  = strVal;
            }
            return el;
        }
       
        public virtual void Deserialize(object owner, string xml)
        {
            try
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(xml);
                XmlElement elThis = doc.SelectSingleElement("this");
                if (elThis != null)
                {
                    foreach (var node in elThis.ChildNodes)
                    {
                        XmlElement el = node as XmlElement;
                        if (el != null)
                        {
                            var property = owner.GetSingleProperty(el.Name);
                            if (el.GetAttributeValue("IsNull").AsBool())
                            {
                                SetNull(property, owner);
                            }
                            else if (property != null)
                            {
                                DeserializeProperty(owner, property, el.InnerText);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                if (ThrowExceptions)
                    throw new Exception("Error in Deserialize Process", ex);
            }
        }
        protected virtual object CreateInstance(Type objectType, string value)
        {
            object objVal = null;
            objVal = Activator.CreateInstance(objectType);
            Deserialize(objVal, value);
            return objVal;
        }
        public virtual T CreateSpecifiedInstance<T>(string xml)
        {
            T val;
            try
            {
                val = (T)CreateInstance(xml);
            }
            catch (Exception ex)
            {
                val = default(T);
                if (ThrowExceptions)
                    throw new Exception("Error in CreateSpecifiedInstance Process", ex);
            }
            return val;
        }
        public virtual object CreateInstance(string xml)
        {
            object owner = null;
            try
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(xml);
                XmlElement elThis = doc.SelectSingleElement("this");
                if (elThis != null)
                {
                    string type = elThis.GetAttributeValue("Type");
                    Type t = Type.GetType(type);
                    owner = CreateInstance(t, xml);
                }
            }
            catch (Exception ex)
            {
                if (ThrowExceptions)
                    throw new Exception("Error in CreateInstance Process", ex);
            }
            return owner;
        }
        protected virtual void SetNull(PropertyInfo property, object owner)
        {
            property.SetValue(owner, null, null);
        }
        protected virtual object GetPropertyValue(PropertyInfo property, string value)
        {
            object obj = null;
            var converter = TypeDescriptor.GetConverter(property.PropertyType);
            if (converter != null && converter.CanConvertFrom(typeof(string)))
                obj = converter.ConvertFrom(value);
            else
                obj = Convert.ChangeType(value, property.PropertyType);
            return obj;
        }
        protected virtual object GetPropertyValue(Type propertyType, string value)
        {
            object objVal = null;
            
            ProgNetObjectSerializer serializer = null;
            var mi = propertyType.GetMethod("Parse");
            if (mi != null)
                objVal = mi.Invoke(null, new object[] { value });
            else if (propertyType.IsEnum)
            {
                objVal = Enum.Parse(propertyType, value);
            }
            else
            {
                if (propertyType != null)
                    serializer = SerializationTools.GetSerializer(propertyType);
                if (serializer != null)
                {
                    objVal = serializer.CreateInstance(propertyType, value);
                }
                else
                {
                    var converter = TypeDescriptor.GetConverter(propertyType);
                    if (converter != null && converter.CanConvertFrom(typeof(string)))
                        objVal = converter.ConvertFrom(value);
                    else
                        objVal = Convert.ChangeType(value, propertyType);
                }
            }
            return objVal;
        }
        protected virtual void DeserializeProperty(object owner, PropertyInfo property, string value)
        {
            object objVal = GetPropertyValue(property.PropertyType, value);
            if (property.CanWrite)
            {
                property.SetValue(owner, objVal, null);
            }
        }

        /// <summary>
        /// Gets the properties to serialize.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public List<PropertyInfo> GetPropertiesToSerialize(object value)
        {
            List<PropertyInfo> list = new List<PropertyInfo>();
            PropertyInfo[] Properties = value.GetType().GetProperties();
            foreach (var p in Properties)
            {
                if (p.CanRead && !Attribute.IsDefined(p, typeof(ProgNetComponents.Serialization.Design.SkipSerializationAttribute)))
                {
                    list.Add(p);
                }
            }
            return list;
        }
    }
    
    public class IListSerializer : ProgNetObjectSerializer
    {
        public override string Serialize(object value, bool beautify = false)
        {
            XmlDocument doc = new XmlDocument();
            IList list = value as IList;
            if (value != null)
            {
                var elThis = doc.AppendChildElement("this");
                elThis.AppendAttribute("Type", value.GetType());
                var items = elThis.AppendChildElement("Items");
                int idx = 0;
                foreach (object o in list)
                {
                    var el = AppendProperty(items, "Item" + idx++, o.GetTypeSafe(), o);
                }
            }
            if (beautify)
                return doc.ToFormatedString();
            else
                return doc.InnerXml;
        }
        public override void Deserialize(object owner, string xml)
        {
            try
            {
                IList list = owner as IList;
                if (list != null)
                {
                    XmlDocument doc = new XmlDocument();
                    doc.LoadXml(xml);
                    XmlElement elThis = doc.SelectSingleElement("this");
                    XmlElement elItems = elThis.SelectSingleElement("Items");
                    Type itemType =null;
                    if (elThis != null)
                    {
                        foreach (var node in elItems.ChildNodes)
                        {
                            itemType = null;
                            XmlElement el = node as XmlElement;
                            if (el != null && el.Name.XMatch("^Item\\d*$"))
                            {
                                var elementThis = el.SelectSingleElement("this");
                                if (elementThis != null)
                                {
                                    string t = elementThis.GetAttributeValue("Type");
                                    if (t != null)
                                        itemType = Type.GetType(t);
                                }
                                var o = GetPropertyValue(itemType, el.InnerXml);
                                list.Add(o);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                if (ThrowExceptions)
                    throw new Exception("Error in Deserialize Process", ex);
            }
        }
    }
}
